<?php
	/**
	 *	KLF Framework
	 *
	 *	Written by Kristian Oye <kristianoye@gmail.com> on Oct 22, 2011
	 *	Copyright (C) 2010-2011
	 */
	namespace KLF\System\Collections
	{
		use KLF\System\KType;

		class KDictionaryIterator implements \Iterator
		{
			private $_current;
			private $_source;
			private $_keys;
	
			public function __construct($source)
			{
				$this->_source =& $source;
				$this->_keys = array_keys($this->_source);
				$this->_current = 0;
				$this->_max = count($this->_source);
			}
			
			public function current()
			{
				return $this->_source[$this->_keys[$this->_current]];
			}
			
			public function key()
			{
				return $this->_keys[$this->_current];
			}
			
			public function next()
			{
				return $this->_current++;
			}
			
			public function rewind()
			{
				$this->_current = 0;
			}
			
			public function valid()
			{
				return ($this->_current < $this->_max);
			}
		}
	
		/**
		 *  Contains a mapping that can potentially be strongly-typed.
		 *
		 *  @package KLF\System\Collections
		 *  @access public
		 *  @author Kristian Oye <kristianoye@gmail.com>
		 *  @since 0.1
		 *  
		 *  @method KDictionary NewInstance
		 *  
		 *  @property string $KeyType
		 *  @property string $ValueType
		 */
		class KDictionary extends KCollection
		{
			/**
			 * @var KType $_rpropKeyType The expected type of the map key.  Null if not enforced.
			 */
			protected $_rpropKeyType;
			
			/**
			 * @var KType $_rpropValueType The expected type of the map value.  Null if not enforced.
			 */
			protected $_rpropValueType;
			
			/**
			 * The actual collection of key/value pairs.
			 * @var mixed
			 */
			private $_Items = array();
			
			/**
			 * Constructs a new KDictionary object
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 1.0
			*/
			public function __construct($keyType=null, $valType=null)
			{
				$this->_Items = array();
				
				if (is_object($keyType) && is_a($keyType, '\KLF\System\KType'))
				{
					$keyType = $keyType;
				}
				else if(!is_null($keyType))
				{
					$keyType = KType::GetTypeByClass($keyType);
				}
				if (is_object($valType) && is_a($valType, '\KLF\System\KType'))
				{
					$valType = $valType;
				}
				elseif (!is_null($valType))
				{
					$valType = KType::GetTypeByClass($valType);
				}
				
				if (!is_null($keyType))
					$this->_rpropKeyType = $keyType;
					
				if (!is_null($valType))
					$this->_rpropValueType = $valType;
			}
			
			/**
			 * Adds a new item to the dictionary
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return KRouteDictionary Returns a reference to the collection.
			*/
			public function Add($key, $val)
			{
				$key = $this->getNormalizedKey($key);
				KCollection::__validateValue($key, 'key', $this->_rpropKeyType);
				KCollection::__validateValue($val, 'value', $this->_rpropValueType);
				$this->_Items[$key] = $val;
				return $this;
			}
			
			/**
			 * Clears all data from the collection.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return KRouteDictionary Returns a reference to the collection.
			*/
			public function Clear()
			{
				$this->_Items = array();
				return $this;
			}
			
			/**
			 * Returns the number of elements in the dictionary.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return int The number of items in the dictionary
			*/
			public function count()
			{
				return \count($this->_Items);
			}
			
			/**
			 * Returns true if the dictionary contains the specified key.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return boolean True if the key exists, false if it does not
			*/
			public function ContainsKey($key)
			{
				$key = $this->getNormalizedKey($key);
				KCollection::__validateValue($key, 'key', $this->_rpropKeyType);
				return isset($this->_Items[$key]);
			}
			
			
			/**
			 * Populates the collection from an array.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return KDictionary Returns a reference to this object
			*/
			public function FromArray($arr)
			{
				foreach($arr as $k => $v)
				{
					$this->Add($k, $v);
				}
				return $this;
			}
			
			/**
			 * Implementation of IteratorAggregate
			 * 
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return KRouteDictionaryIterator Iterator object
			 */
			public function getIterator()
			{
				return new KDictionaryIterator($this->_Items);
			}
			
			/**
			 * Allows descendent-classes to do something with key (if needed)
			 * 
			 * @param mixed $key
			 */
			protected function GetNormalizedKey($key)
			{
				return $key;
			}
			
			/**
			 * Checks to see if a particular index exists in the list.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @param mixed $offset
			 * @return boolean true if the index exists, false if not.
			*/
			public function offsetExists($offset)
			{
				$offset = $this->GetNormalizedKey($offset);
				KCollection::__validateValue($offset, 'key', $this->_rpropKeyType);
				return isset($this->_Items[$offset]); 
			}
			
			/**
			 * Returns the value associated with a particular index.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * 
			 * @param mixed $offset
			 * @return mixed The value at the given offset.
			*/
			public function offsetGet($offset)
			{
				$offset = $this->GetNormalizedKey($offset);
				KCollection::__validateValue($offset, 'key', $this->_rpropKeyType);
				return (isset($this->_Items[$offset]) ? 
					$this->_Items[$offset] : null);
			}
			
			/**
			 * Sets the value at a particular offset in the list.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * 
			 * @param mixed $offset
			 * @param mixed $value
			 * @return mixed Returns the newly set value.
			*/
			public function offsetSet($offset, $value)
			{
				$offset = $this->GetNormalizedKey($offset);
				KCollection::__validateValue($offset, 'key', $this->_rpropKeyType);
				KCollection::__validateValue($value, 'value', $this->_rpropValueType);
				
				if (is_null($offset))
				{
					return ($this->_Items[] = $value);
				}
				else
				{
					return ($this->_Items[$offset] = $value);
				}
			}
			
			/**
			 * Unsets a value at the specified offset.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * 
			 * @param mixed $offset
			*/
			public function offsetUnset($offset)
			{
				$offset = $this->getNormalizedKey($offset);
				KCollection::__validateValue($offset, 'key', $this->_rpropKeyType);
				unset($this->_Items[$offset]);
			}
			
			/**
			 * Removes the specified key and value from the dictionary.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * 
			 * @param mixed $key
			 * @return boolean Returns true if the item was removed, false if it did not exist or could not be removed.
			*/
			public function Remove($key)
			{
				$key = $this->getNormalizedKey($key);
				KCollection::__validateValue($key, 'key', $this->_rpropKeyType);
				if ($this->ContainsKey($key))
				{
					unset($this->_Items[$key]);
					return !$this->ContainsKey($key);
				}
				return false;
			}
		}
	}